﻿using System.Collections.Generic;
using System.Text;
using FunctionalCLR.Core.Function;
using FunctionalCLR.Core.Function.Arithmetics;
using FunctionalCLR.Core.Function.Flow;
using FunctionalCLR.Core.Function.Logic;
using FunctionalCLR.Core.Functions.Maths;

namespace FunctionalCLR.Core {
    public class Parser {

        private int currentParethesisDepth = 0;

        private Stack<Node> operandStack;

        private Stack<Node> operatorStack;

        private Stack<ParameterCountStruct> ParameterCountStack {
            get;
            set;
        }

        public IContext Context {
            get;
            set;
        }

        public IParserFactory ParserFactory {
            get;
            set;
        }

        protected Lexer lexer;

        public IFunctionManager FunctionManager {
            get;
            set;
        }

        public IConstantManager ConstantManager {
            get;
            set;
        }

        public List<string> Identifiers {
            get;
            set;
        }

        private Parser() {
            this.operandStack = new Stack<Node>();
            this.operatorStack = new Stack<Node>();
            this.ParameterCountStack
                = new Stack<ParameterCountStruct>();
            this.Identifiers = new List<string>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buff"></param>
        public Parser(string buff)
            : this() {
            lexer = new Lexer(buff);
        }

        public Parser(Lexer lexer)
            : this() {
            this.lexer = lexer;
        }

        public Parser(string buff, IContext context)
            : this(buff) {
            this.Context = context;
        }

        public Parser(Lexer lexer, IContext context)
            : this(lexer) {
            this.Context = context;
        }

        /// <summary>
        /// Execute the parse
        /// </summary>
        /// <returns></returns>
        public Node Parse() {
            Token token;
            Node root = null;

            while (lexer.HasMoreTokens()) {
                token = lexer.NextToken();
                switch (token.TokenType) {
                    case TokenType.Identifier:
                        char nc = lexer.PeekChar();
                        if (nc == '(') {
                            FuncNode fn = FunctionManager.Get(token.Value);
                            if (fn == null) {
                                throw new FunctionNotDefinedException(token.Value);
                            }

                            this.operatorStack.Push(fn);
                            ParameterCountStruct pcs = new ParameterCountStruct();
                            pcs.numberOfParameters = 0;
                            pcs.parethesisDepth
                                = this.currentParethesisDepth + 1;

                            this.ParameterCountStack.Push(pcs);
                        } else {
                            if (ConstantManager.IsConstant(token.Value)) {
                                root = new NumberNode();
                                root.Value = ConstantManager.GetConstant(token.Value);
                            } else {
                                root = GetIdenitiferNode(token.Value);
                                root.Value = token.Value;

                                if (!this.Identifiers.Contains(token.Value)) {
                                    this.Identifiers.Add(token.Value);
                                }
                            }

                            operandStack.Push(root);
                        }
                        break;
                    case TokenType.Number:
                        root = new NumberNode();
                        root.Value = token.Value;
                        operandStack.Push(root);
                        break;
                    case TokenType.Assign:
                        root = new AssignNode(Context);
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        operatorStack.Push(root);
                        break;
                    case TokenType.Declare:
                        root = new DeclareNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        operatorStack.Push(root);
                        break;
                    case TokenType.Add:
                        root = new AddNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Sub:
                        if (lexer.LastToken == null
                            || lexer.LastToken.TokenType == TokenType.LeftBrace
                            || lexer.LastToken.TokenType == TokenType.Comma
                            ) {
                            root = new NumberNode();
                            root.Value = "0";
                            this.operandStack.Push(root);
                            // there is nothing ahead
                            // it is a negative number
                        }
                        root = new SubNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Mul:
                        root = new MulNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Div:
                        root = new DivNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Percent:
                        root = new RemainderNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Square:
                        root = new PowerNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Sqrt:
                        root = new LogNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.And:
                        root = new AndNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Or:
                        root = new OrNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Not:
                        root = new NotNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Nand:
                        root = new NandNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Nor:
                        root = new NorNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.Equal:
                        root = new EqualNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.NotEqual:
                        root = new NotEqualNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.LargerEqualTo:
                        root = new LargerEqualToNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.LargerThan:
                        root = new LargerThanNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.LessEqualTo:
                        root = new LessEqualToNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.LessThan:
                        root = new LessThanNode();
                        ProcessAllOperatorsWhosePrecedenceIsHigerThan(
                            ((FuncNode)root).Precedence);
                        this.operatorStack.Push(root);
                        break;
                    case TokenType.LeftBracket:
                        ParameterCountStruct pcb = new ParameterCountStruct();
                        pcb.numberOfParameters = 0;
                        pcb.parethesisDepth = this.currentParethesisDepth + 1;

                        this.operatorStack.Push(new BlockNode());
                        this.currentParethesisDepth++;

                        ParameterCountStack.Push(pcb);
                        this.operatorStack.Push(new EmbraceNode());
                        this.Parse();
                        //stack.Push(root);
                        break;
                    case TokenType.LeftSquareBrace:
                        ParameterCountStruct pcc = new ParameterCountStruct();
                        pcc.numberOfParameters = 0;
                        pcc.parethesisDepth = this.currentParethesisDepth + 1;

                        this.operatorStack.Push(new ListNode());
                        this.currentParethesisDepth++;

                        ParameterCountStack.Push(pcc);
                        this.operatorStack.Push(new EmbraceNode());
                        this.Parse();
                        //stack.Push(root);
                        break;
                    case TokenType.LeftBrace:
                        this.currentParethesisDepth++;
                        //this.operatorNodeStack.Push(new GroupNode());
                        this.operatorStack.Push(new EmbraceNode());
                        this.Parse();
                        //stack.Push(root);
                        break;
                    case TokenType.RightBracket:
                    case TokenType.RightSquareBrace:
                    case TokenType.RightBrace:
                        if (operatorStack.Peek() != null) {
                            // okay, let's finish the last expression
                            // until EmbraceNode

                            bool done;
                            do {
                                done = ProcessAllOperatorsWhosePrecedenceIsHigerThan(-1);
                            }
                            while (!done);

                            if (this.ParameterCountStack.Count > 0) {
                                ParameterCountStruct p
                                    = this.ParameterCountStack.Pop();
                                if (p.parethesisDepth == currentParethesisDepth) {
                                    // let's check out whether the number of 
                                    // parameters is zero or one
                                    char lastChar = lexer.PreviousChar(2);
                                    if (lastChar == '(') {
                                        p.numberOfParameters = 0;
                                    } else {
                                        p.numberOfParameters++;
                                    }
                                }
                                this.ParameterCountStack.Push(p);
                            }
                            this.currentParethesisDepth--;
                        }
                        return null;
                    case TokenType.Comma:
                        if (operatorStack.Peek() != null) {
                            bool done;
                            do {
                                done = ProcessAllOperatorsWhosePrecedenceIsHigerThan(-1);
                            }
                            while (!done);
                        }
                        if (this.ParameterCountStack.Count > 0) {
                            ParameterCountStruct pcx = this.ParameterCountStack.Pop();
                            if (pcx.parethesisDepth == currentParethesisDepth) {
                                pcx.numberOfParameters++;
                            }
                            this.ParameterCountStack.Push(pcx);
                        }

                        this.operatorStack.Push(new EmbraceNode());
                        break;
                    case TokenType.String:
                        StringNode node = new StringNode();
                        node.Value = lexer.ReadUntil('\"');
                        this.operandStack.Push(node);
                        break;
                    case TokenType.Comment:
                        break;
                    default:
                        break;
                }
            }

            if (operatorStack.Count > 0) {
                do {
                    ProcessAllOperatorsWhosePrecedenceIsHigerThan(-1);
                }
                while (operandStack.Count > 1);
            }
            return operandStack.Pop();
        }

        protected void AttachChildNodeToParentOperatorNode() {
            Node current = operandStack.Pop();
            Node on = this.operatorStack.Pop();
            if (on.GetType().IsSubclassOf(typeof(FuncNode))) {
                FuncNode funcNode
                    = (FuncNode)on;
                funcNode.Children.Add(current);
            }

            this.operatorStack.Push(on);
        }

        /// <summary>
        /// Post process the identifier node
        /// </summary>
        /// <param name="root"></param>
        public virtual IdentifierNode GetIdenitiferNode(string value) {
            IdentifierNode node = new IdentifierNode();
            node.Value = value;

            return node;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="tokenType"></param>
        private bool ProcessAllOperatorsWhosePrecedenceIsHigerThan(
            int precedenceOfTheUpcomingOperator) {
            if (this.operatorStack.Count == 0) {
                return false;
            }

            // get the current operator
            FuncNode currentOperator
                = (FuncNode)this.operatorStack.Peek();
            if (currentOperator.GetType().Equals(
                typeof(EmbraceNode))) {
                if (precedenceOfTheUpcomingOperator == -1) {
                    // if the most recent expression is finished
                    this.operatorStack.Pop();
                    return true;
                } else {
                    return false;
                }
            }

            // test if we need to process the current operator
            while (currentOperator.Precedence
                >= precedenceOfTheUpcomingOperator) {
                // if the current operator has higher precedence
                // of the upcoming one
                this.operatorStack.Pop();
                if (currentOperator is SingularOperatorNode) {
                    SingularOperatorNode son
                        = (SingularOperatorNode)currentOperator;
                    son.Operand = operandStack.Pop();
                } else if (currentOperator is OperatorNode) {
                    OperatorNode on
                        = (OperatorNode)currentOperator;
                    on.Right = operandStack.Pop();
                    on.Left = operandStack.Pop();
                } else if (currentOperator is FuncNode) {
                    FuncNode fn = (FuncNode)currentOperator;
                    Node child = null;
                    int order = fn.ParameterCount;

                    ParameterCountStruct pcs = this.ParameterCountStack.Pop();
                    for (int index = 0; index < pcs.numberOfParameters; index++) {
                        child = operandStack.Pop();
                        fn.Children.Add(child);
                    }
                }
                operandStack.Push(currentOperator);
                if (operatorStack.Count > 0) {
                    currentOperator = (FuncNode)operatorStack.Peek();
                    if (currentOperator is EmbraceNode) {
                        break;
                    }
                } else {
                    break;
                }
            }

            return false;
        }
    }
}
